Android ptrace函数的实现

首先看sys/ptrace.h

/bionic/libc/include/sys/ptrace.h

我们在调用的时候使用的是PTRACE_的导出符号,glibc也导出了PT_开头的符号。PTRACE_开头的符号定义在/bionic/libc/kernel/uapi/linux/ptrace.h中


都是int型的数字,回到ptrace函数的定义,头文件中写的是一个不定参数的形式
long ptrace(int, ...);


继续跟进__ptrace函数



__ptrace函数使用汇编实现,其中____NR_ptrace是系统调用号,根据调用号进入kernel层即linux内核的逻辑。此处__NR_ptrace的调用号为26,swi #0 为产生中断,切换到内核模式


内核的实现是在/kernel/ptrace.c中
PTRACE_TRACEME调用

   static int ptrace_traceme(void)
382{
383 int ret = -EPERM;
384
385 write_lock_irq(&tasklist_lock);
386 /* Are we already being traced? */
387 if (!current->ptrace) {
388     ret = security_ptrace_traceme(current->parent);
389     /*
390      * Check PF_EXITING to ensure ->real_parent has not passed
391      * exit_ptrace(). Otherwise we don't report the error but
392      * pretend ->real_parent untraces us right after return.
393      */
394     if (!ret && !(current->real_parent->flags & PF_EXITING)) {
395         current->ptrace = PT_PTRACED;
396         __ptrace_link(current, current->real_parent);
397     }
398 }
399 write_unlock_irq(&tasklist_lock);
400
401 return ret;
402}

__ptrace_link函数

void __ptrace_link(struct task_struct *child, struct task_struct *new_parent)
38{
39  BUG_ON(!list_empty(&child->ptrace_entry));
40  list_add(&child->ptrace_entry, &new_parent->ptraced);
41  child->parent = new_parent;
42}

函数首先判断当前进程有没有正在被traced,如果为否进行一系列安全检查将当前进程设置为PT_PTRACED,并调用__ptrace_link函数将子进程链接到父进程的ptrace链表中。之后再进行系统调用的时候,内核就会判断当前进程是否被设置为traced,如果是内核将向该进程发送SIGTRAP信号。该信号将导致当前进程停止。并将控制权交给父进程。

PTRACE_ATTACH调用ptrace_attach

static int ptrace_attach(struct task_struct *task, long request,
274          unsigned long addr,
275          unsigned long flags)
276{
277 bool seize = (request == PTRACE_SEIZE);
278 int retval;
279
280 retval = -EIO;
281 if (seize) {
282     if (addr != 0)
283         goto out;
284     if (flags & ~(unsigned long)PTRACE_O_MASK)
285         goto out;
286     flags = PT_PTRACED | PT_SEIZED | (flags << PT_OPT_FLAG_SHIFT);
287 } else {
288     flags = PT_PTRACED;
289 }
290
291 audit_ptrace(task);
292
293 retval = -EPERM;
294 if (unlikely(task->flags & PF_KTHREAD))
295     goto out;
296 if (same_thread_group(task, current))
297     goto out;
298
299 /*
300  * Protect exec's credential calculations against our interference;
301  * SUID, SGID and LSM creds get determined differently
302  * under ptrace.
303  */
304 retval = -ERESTARTNOINTR;
305 if (mutex_lock_interruptible(&task->signal->cred_guard_mutex))
306     goto out;
307
308 task_lock(task);
309 retval = __ptrace_may_access(task, PTRACE_MODE_ATTACH);
310 task_unlock(task);
311 if (retval)
312     goto unlock_creds;
313
314 write_lock_irq(&tasklist_lock);
315 retval = -EPERM;
316 if (unlikely(task->exit_state))
317     goto unlock_tasklist;
318 if (task->ptrace)
319     goto unlock_tasklist;
320
321 if (seize)
322     flags |= PT_SEIZED;
323 rcu_read_lock();
324 if (ns_capable(__task_cred(task)->user_ns, CAP_SYS_PTRACE))
325     flags |= PT_PTRACE_CAP;
326 rcu_read_unlock();
327 task->ptrace = flags;
328
329 __ptrace_link(task, current);
330
331 /* SEIZE doesn't trap tracee on attach */
332 if (!seize)
333     send_sig_info(SIGSTOP, SEND_SIG_FORCED, task);
334
335 spin_lock(&task->sighand->siglock);
336
337 /*
338  * If the task is already STOPPED, set JOBCTL_TRAP_STOP and
339  * TRAPPING, and kick it so that it transits to TRACED.  TRAPPING
340  * will be cleared if the child completes the transition or any
341  * event which clears the group stop states happens.  We'll wait
342  * for the transition to complete before returning from this
343  * function.
344  *
345  * This hides STOPPED -> RUNNING -> TRACED transition from the
346  * attaching thread but a different thread in the same group can
347  * still observe the transient RUNNING state.  IOW, if another
348  * thread's WNOHANG wait(2) on the stopped tracee races against
349  * ATTACH, the wait(2) may fail due to the transient RUNNING.
350  *
351  * The following task_is_stopped() test is safe as both transitions
352  * in and out of STOPPED are protected by siglock.
353  */
354 if (task_is_stopped(task) &&
355     task_set_jobctl_pending(task, JOBCTL_TRAP_STOP | JOBCTL_TRAPPING))
356     signal_wake_up_state(task, __TASK_STOPPED);
357
358 spin_unlock(&task->sighand->siglock);
359
360 retval = 0;
361 unlock_tasklist:
362 write_unlock_irq(&tasklist_lock);
363 unlock_creds:
364 mutex_unlock(&task->signal->cred_guard_mutex);
365 out:
366 if (!retval) {
367     wait_on_bit(&task->jobctl, JOBCTL_TRAPPING_BIT,
368             TASK_UNINTERRUPTIBLE);
369     proc_ptrace_connector(task, PTRACE_ATTACH);
370 }
371
372 return retval;
373}

整个函数的流程总结如下(分析来自linux3.5.4 ptrace源码分析二 新增部分修改)
1. 判断请求是PTRACE_SEIZE还是PTRACE_ATTACH,如果ptrace请求为PTRACE_SEIZE,则检查其参数是否正确,参数有误则退出
2. 判断task进程是否为kernel thread(PF_KTHREAD),调用same_thread_group(task,current),判断task是否和current进程在同一个线程组,查看current进程是否有权限追踪task进程,不符合要求则退出
3. 设置子进程task->ptrace = PT_TRACED,被跟踪状态,如果当前进程拥有CAP_SYS_PTRACED,设置task->ptrace |= PT_TRACE_CAP
4. 调用__ptrace_link(task, current),将task->ptrace_entry链接到current->ptraced链表中,并设置当前进程为被跟踪进程的新的父进程。
5. 如果是PTRACE_ATTACH请求(PTRACE_SEIZE请求不会停止被追踪进程),则调用send_sig_info(SIGSTOP,SEND_SIG_FORCED, task);发送SIGSTOP信号,中止task运行,设置task->state为TASK_STOPPED
6. 等待task->jobctl的JOBCTL_TRAPPING_BIT位被清零,阻塞时进程状态被设置为TASK_UNINTERRUPTIBLE并引发进程调度
PTRACE_ATTACH处理的方式与PTRACE_TRACEME处理的方式不同,PTRACE_ATTACH会使父进程向子进程发送SIGTRAP信号,如果子进程停止,父进程的wait操作则会被唤醒,从而成功attach。

而PTRACE_TRACEME只是表明该进程(child)想被trace的意愿。如果一个进程调用了PTRACE_TRACEME,那么该进程处理信号的方式将会变得不同。比如:如果一个进程正在运行,此时输入ctrl+c(SIGINT),则该进程直接退出。但是,如果该进程中有ptrace(PTRACE_TRACEME,0,NULL,NULL)。即该进程主动要求被跟踪,那么,当输入CTRL+C时,该进程将会处于stopped的状态。

PTRACE_PEEKDATA/PEEKTEXT
PTRACE_POKEDATA/POKETEXT

int generic_ptrace_peekdata(struct task_struct *tsk, unsigned long addr,
1076                unsigned long data)
1077 {
1078    unsigned long tmp;
1079    int copied;
1080
1081    copied = access_process_vm(tsk, addr, &tmp, sizeof(tmp), 0);
1082    if (copied != sizeof(tmp))
1083        return -EIO;
1084    return put_user(tmp, (unsigned long __user *)data);
1085 }

7int generic_ptrace_pokedata(struct task_struct *tsk, unsigned long addr,
1088                unsigned long data)
1089{
1090    int copied;
1091
1092    copied = access_process_vm(tsk, addr, &data, sizeof(data), 1);
1093    return (copied == sizeof(data)) ? 0 : -EIO;
1094}
1095

int access_process_vm(struct task_struct *tsk, unsigned long addr, void *buf, int len, int write)
2058 {
2059    struct mm_struct *mm;
2060
2061    if (addr + len < addr)
2062        return 0;
2063
2064    mm = get_task_mm(tsk);
2065    if (!mm)
2066        return 0;
2067
2068    len = __access_remote_vm(tsk, mm, addr, buf, len, write);
2069
2070    mmput(mm);
2071    return len;
2072 }

static int __access_remote_vm(struct task_struct *tsk, struct mm_struct *mm,
2006        unsigned long addr, void *buf, int len, int write)
2007 {
2008    struct vm_area_struct *vma;
2009
2010    down_read(&mm->mmap_sem);
2011
2012    /* the access must start within one of the target process's mappings */
2013    vma = find_vma(mm, addr);
2014    if (vma) {
2015        /* don't overrun this mapping */
2016        if (addr + len >= vma->vm_end)
2017            len = vma->vm_end - addr;
2018
2019        /* only read or write mappings where it is permitted */
2020        if (write && vma->vm_flags & VM_MAYWRITE)
2021            copy_to_user_page(vma, NULL, addr,
2022                     (void *) addr, buf, len);
2023        else if (!write && vma->vm_flags & VM_MAYREAD)
2024            copy_from_user_page(vma, NULL, addr,
2025                        buf, (void *) addr, len);
2026        else
2027            len = 0;
2028    } else {
2029        len = 0;
2030    }
2031
2032    up_read(&mm->mmap_sem);
2033
2034    return len;
2035 }

2#undef copy_to_user_page
83static inline void copy_to_user_page(struct vm_area_struct *vma,
84                       struct page *page,
85                       unsigned long vaddr,
86                       void *dst, void *src, int len)
87{
88  memcpy(dst, src, len);
89  if (vma->vm_flags & VM_EXEC) {
90      flush_icache_range((unsigned long) dst,
91      (unsigned long) dst + len);
92  }
93}

获取内存的值和写入内存值逻辑上差不多,内核是通过操作区域的属性来判断写入还是读取。最终由memcpy函数实现内存的拷贝。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容